# 路径分隔符

  • 在Windows 系统上为分号(;),在 UNIX 系统上为冒号(:)

# 文件名称分隔符

  • 在 Windows 系统上为反斜线(\),在 UNIX 系统上为斜线(/)
  • Java 程序中的反斜线表示转义字符,所以如果需要在 Windows 的路径下包括反斜线,则应该使用两条反斜线,如 F:\abc\test.txt
  • Java 程序支持将斜线(/)当成平台无关的文件分隔符

# 行分隔符

  • Windows 系统上为 \r,UNIX/Linux/BSD 等系统上为 \n

# 路径

  • 绝对路径:在 Windows 等系统上,路径开头由盘符和一个 “:” 组成,在 UNIX 系统上,路径名开头是一条斜线(/)
  • 相对路径:用户的工作路径,运行 Java 虚拟机时所在的路径(默认)

# 文本文件

  • 当二进制文件里的内容能被正常解析成字符时,则该二进制文件就变成了文本文件

# Charset 类

  • 编码(Encode)和解码(Decode)
  • 标准字符集常量定义在 StandardCharsets 类中,如 UTF_8
  • Java 默认使用 Unicode 字符集(Unicode 符号集只规定了符号的二进制代码)
  • 常见的字符集:
    ASCII:占一个字节,只能包含128个符号,不能表示汉字
    ISO-8859-1(又称 latin-1):占一个字节,收录西欧语言,不能表示汉字
    ANSI:占两个字节,在简体中文的操作系统中 ANSI 指的是 GB2312
    GB2312、GBK、GB18030:占两个字节,支持中文
    UTF-8:是一种针对 Unicode 的可变长度字符编码,又称万国码,是 Unicode 的实现方式之一
  • 存储字母和数字:无论是什么字符集都占 1 个字节
    存储汉字:GBK 家族占 2 个字节,UTF-8 占 3 个字节,UTF-16 占 2 个字节
String result = new String("汉字".getBytes("UTF-8"), "ISO-8859-1"); // 乱码
String message = new String(result.getBytes("ISO-8859-1"), "UTF-8");
// GBK 和 UTF-8 之间的乱码无法解决
1
2
3

# File 类

  • 在 java.io 包下,只能操作文件和目录,不能访问文件内容本身

  • 全局静态常量

    1. 路径分隔符(在 UNIX 系统上,该字符为 ':', 在 Microsoft Windows 系统上,该字符为 ';'):char pathSeparatorChar、String pathSeparator
    2. 文件名称分隔符(在 UNIX 系统上,该字符为 '/',在 Microsoft Windows 系统上,该字符为 '\\'):char separatorChar、String separator
  • 构造器
    File(String pathname):将给定的路径名字符串 pathname 转换为抽象路径名来创建 File 对象
    File(String parent, String child):
    File(File parent, String child):根据 parent 抽象路径名和 child 路径名字符串创建 File 对象

# 访问文件和目录

# 访问文件名

  • File file = new File("C:/abc/123.txt");
  • String getName():返回此 File 对象所表示的文件名目录名,123.txt
  • String getPath():返回此 File 对象所对应的路径名,C:/abc/123.txt
  • File getAbsoluteFile():返回此 File 对象的绝对路径
  • String getAbsolutePath():返回此 File 对象所对应的绝对路径名,C:/abc/123.txt
  • File getParentFile():返回此 File 对象所对应的父路径,如果没有,返回 null,C:/abc
  • String getParent():返回此 File 对象所对应的父路径名

# 检测文件

  • boolean exists():判断 File 对象所对应的文件或目录是否真实存在
  • boolean isFile():判断 Hie 对象所对应的是否是文件
  • boolean isDirectory():判断 File 对象所对应的是否是目录
  • boolean isAbsolute():判断 File 对象是否是绝对路径
  • boolean canRead():判断 File 对象所对应的文件和目录是否可读
  • boolean canWrite():判断 File 对象所对应的文件和目录是否可写
  • boolean canExecute():判断应用程序是否可以执行此 File 对象所对应的文件或目录

# 获取常规文件信息

  • long length():获取文件内容的长度(单位为字节)(如果此路径名表示一个目录,则返回值是不确定的)
  • long lastModified():获取文件的最后修改时间

# 操作文件

  • boolean createNewFile():当此 File 对象所对应的文件不存在但对应的父路径存在时,新建一个该 File 对象所指定的文件
  • static File createTempFile(String prefix, String suffix):在默认的临时文件目录中创建一个临时的文件,使用给定前缀、 系统生成的随机数和给定后缀作为文件名(prefix 参数至少是 3 字节长,suffix 参数为 null 时,将使用默认的后缀“.tmp”)
  • static File createTempFile(String prefix, String suffix, File directory):在 directory 所指定的目录中创建一个临时的文件,使用给定前缀、 系统生成的随机数和给定后缀作为文件名
  • boolean mkdir():当此 File 对象所对应的目录不存在但对应的父目录存在时,新建一个 File 对象所对应的目录
  • boolean mkdirs():新建一个 File 对象所对应的目录包括所有必需但不存在的父目录
  • boolean delete():删除 File 对象所对应的文件或目录
  • void deleteOnExit():注册一个删除钩子,指定当 Java 虚拟机退出时,删除 File 对象所对应的文件或目录
  • boolean renameTo(File dest):用指定的路径名重新命名对 File 对象所对应的文件或路径
  • File[] listFiles():列出 File 对象的所有子文件和子目录的绝对路径,返回 File 数组
  • String[] list():列出 File 对象的所有子文件名和子目录的名称,返回 String 数组
  • static File[] listRoots():列出系统所有的根路径

# 文件过滤器

  • String[] list(FilenameFilter filter)
  • File[] listFiles(FilenameFilter filter):列出 File 对象的所有符合条件的子文件和目录,返回 File 数组
  • FilenameFilter 接口中的抽象方法:boolean accept(File dir, String name)
  • 底层依次对指定 File 的所有子目录或者文件进行迭代,如果该方法返回 true,则 list() 方法会列出该子目录或者文件
// 列出 D:\JavaApps 目录下以“.java”结尾的文件(不包括子目录下)
File dir = new File("D:/JavaApps");
File[] fs = dir.listFiles(new FilenameFilter() {
    // 底层传给 dir 的是 this, 传给 name 的是 this 中的子文件名或子目录名
    public boolean accept(File dir, String name) {
        return new File(dir, name).isFile() && name.endsWith(".java");
    }
});
1
2
3
4
5
6
7
8

# IO 流概述

  • 所有传统的流类型(类或抽象类)都放在 java. io 包下
  • 流对象封装了用于保存输入或输出信息的缓冲空间,以及针对这个缓冲空间进行操作的方法
  • 步骤:
    1. 创建节点对象
    2. 创建 IO 流对象
    3. 具体的 IO 操作(read()、write())
    4. 关闭 IO 流对象
  • 输入流和输出流:从程序运行所在内存的角度来划分的
  • 字节流和字符流
    字节流操作的数据单元是 8 位的字节
    字符流操作的数据单元是 16 位的字符(2个字节)
  • 节点流和处理流
    • 节点流,和实际的输入/输出节点连接,流的构造器参数是一个物理节点
    • 处理流(包装流),对已存在的节点流进行连接或封装,流的构造器参数是已经存在的流
    • 使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的 I/O 设备、 文件交互
  • 4 个抽象基类:InputStream、OutputStream、Reader、Writer
  • InputStreamReader、OutputStreamWriter 类是从字节流到字符流的桥梁

IO流分类

# 输入流:InputStream 和 Reader

  • 创建字节/字符数组作为缓冲区,read in
    1. int read():从输入流中读取一个字节/字符,返回所读取的字节数据/字符数据,如果已读到流的末尾,则返回 -1
    2. int read(byte[]/char[] buf):从输入流中最多读取 buf.Iength 个字节/字符的数据,并将其存储在字节数组/字符数组 buf 中,返回实际读取的字节数/字符总数,如果已读到流的末尾,则返回 -1
    3. int read(byte[]/char[] buf, int off, int len):从输入流中最多读取 len 个字节/字符的数据,并将其存储在字节数组/字符数组 buf 中,从数组的 off 位置开始放入,返回实际读取的字节数/字符总数,如果已读到流的末尾,则返回 -1
  • 标准字节输入流,读取键盘的输入:System.in

# 输出流:OutputStream 和 Writer

  • write out

    1. void write(int c):将指定的字节/字符 c 输出到输出流中
    2. void write(byte[]/char[] buf):将字节数组/字符数组 buf 中的数据输出到指定输出流中
    3. void write(byte[]/char[] buf,int off,int len):将字节数组/字符数组 buf 中从 off 位置开始,长度为 len 的字节/字符输出到输出流中
    4. void flush():刷新此输出流并强制写出所有缓冲的输出字节/字符(仅对缓冲输出流和字符输出流起作用)
  • Writer 类(字符输出流)中还包含如下两个方法

    1. void write(String str):将 str 字符串里包含的字符输出到指定输出流中
    2. void write(String str, int off, int len):将 str 字符串里从 off 位置开始,长度为 len 的字符输出到指定输出流中
// 关闭资源的正确方式
// 方式 1
private static void test1() {
    // 在 try...catch 外声明流对象
    BufferedInputStream in = null;
    BufferedOutputStream out = null;
    try {
        // 文件拷贝
        in = new BufferedInputStream(new FileInputStream("src.txt"));
        out = new BufferedOutputStream(new FileOutputStream("dest.txt", true));
        byte[] buffer = new byte[8192];
        int len = -1;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 在 finally 块中,每个流对象使用独立的 try...catch 关闭,并且在关闭前判断该流对象是否为 null
        try {
            if (in != null) {
                in.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 方式 2:Java 7 提供的自动资源关闭
private static void test2() {
    try (    // 创建实现了 AutoCloseable 接口的流对象
            BufferedInputStream in = new BufferedInputStream(new FileInputStream("src.txt"));
            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("dest.txt", true));
            // ByteArrayOutputStream out = new ByteArrayOutputStream();
    ) {
        byte[] buffer = new byte[8192];
        int len = -1;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        // byte[] bytes = out.toByteArray();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 节点流

# 文件流

  • 访问文件

# FilelnputStream 和 FileReader

  • 构造器(当指定 File 对象对应的文件不存在时,会在对应的目录下新建该文件
    FileInputStream(File file)、FileInputStream(String name)
    FileReader(File file)、FileReader(String fileName)

    FileReader 是以当前机器的默认字符集来读取文件的(Charset.defaultCharset().name()),如果需要指定字符集,应使用 InputStreamReader 和 FileInputStream

# FileOutputStream 和 FileWrite

  • 构造器(当指定 File 对象对应的文件不存在时,会在对应的目录下新建该文件
    FileOutputStream(File file, boolean append)、FileOutputStream(String name, boolean append)
    FileWriter(File file, boolean append)、FileWriter(String fileName, boolean append)

    FileWriter 是以当前机器的默认字符集来写文件的 Charset.defaultCharset().name()

# 数组流(内存流)

  • 访问数组:字节流以字节数组为节点,字符流以字符数组为节点
    ByteArrayInputStream、ByteArrayOutputStream
    CharArrayReader、CharArrayWriter

  • 访问字符串
    StringReader、StringWriter

  • ByteArrayInputStream / ByteArrayOutputStream 等都是对内存中的字节数据的访问,是一个虚拟的流,没有占用网络、磁盘文件等资源,所以没有关闭的必要,实现上也是空,但是对流进行关闭是一个好习惯

# 管道流(线程通讯流)

  • 将 A 线程的管道输出流连和 B 线程的管道输入流连接(connect)来实现线程之间通信功能
  • PipedlnputStream、 PipedOutputStream
    PipedReader、 PipedWriter

# Scanner 类进行输入操作

  • java.util 包中,可以使用正则表达式来解析基本类型和字符串的简单文本扫描器

  • 构造器
    Scanner(File source, String charsetName)
    Scanner(InputStream source, String charsetName)
    Scanner(String source):创建一个从指定字符串扫描的扫描器

  • 实例方法:(xxx表示数据类型,如byte、int 、boolean等)
    boolean hasNextXxx():判断此扫描器输入信息中的下一个标记是否可以解释为一个 xxx 值
    Xxx nextXxx():将输入信息的下一个标记扫描为一个 xxx 值
    boolean hasNextLine() :如果在此扫描器的输入中存在另一行,则返回 true
    String nextLine():此扫描器执行当前行,并返回跳过的输入信息
    Scanner useDelimiter(Pattern pattern):将此扫描器的分隔模式设置为指定模式,返回 this

# Properties 类加载配置文件

  • void load(InputStream inStream)
    void store(OutputStream out, String comments)

# 访问其它进程

  • 以子进程为节点:用于本进程读写其他进程中的数据

  • Process 类中用于让程序和其子进程进行通信的实例方法:
    InputStream getInputStream():获取子进程的输入流
    OutputStream getOutputStream():获取子进程的输出流
    InputStream getErrorStream():获取子进程的错误流

# RandomAccessFile

  • 可以自由访问文件的任意地方

  • 构造器
    RandomAccessFile(File file, String mode)
    RandomAccessFile(String name, String mode)
    模式参数:"r"、"rw"、"rws" 或 "rwd"

  • 实例方法
    void writeXxx(xxx v):按 x 个字节将 v 入该文件
    void readXxx(xxx v):从此文件读取一个 xxx(注意:writeXxx 和 readXxx 必须要对应起来,writeByte 写出的数据,此时只能使用 readByte 读取回来)
    void writeUTF(String str):使用 modified UTF-8 编码将一个字符串写入该文件(比 UTF-8 多 2 个字节)
    String readUTF():从此文件读取一个字符串
    long getFilePointer():返回此文件中的当前偏移量
    void seek(long pos):设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作
    int skipBytes(int n):尝试跳过输入的 n 个字节以丢弃跳过的字节
    long length():返回此文件的长度
    void setLength(long newLength):设置此文件的长度

# 处理流

  • 关闭输入/输出流资源时,只要关闭最上层的处理流即可

# 缓冲流

  • 缓冲区大小为 8192 个字节或字符

# BufferedInputStream 和 BufferedReader

  • 构造器
    BufferedInputStream(InputStream in)、BufferedReader(Reader in)

  • BufferedReader 类(字符缓冲输入流)中特有的方法
    String readLine():读取一个文本行(换行 '\n' 或回车 '\r'),如果已到达流末尾,则返回 null

# BufferedOutputStream 和 BufferedWriter

  • 构造器
    BufferedOutputStream(OutputStream out)、BufferedWriter(Writer out)

  • BufferedWriter 类(字符缓冲输出流)中特有的方法
    void newLine():写入一个行分隔符(由系统属性 line.separator 定义)

# 转换流

  • 将字节流转换成字符流:InputStreamReader、OutputStreamWriter

  • 构造器
    InputStreamReader(InputStream in, String charsetName)
    OutputStreamWriter(OutputStream out, String charsetName)

  • 转换流特有的方法
    String getEncoding():返回此流使用的字符编码的名称

# 顺序流(合并流)

  • 把多个字节输入流合并成一个字节输入流对象
  • SequenceInputStream
    构造器:SequenceInputStream(InputStream s1, InputStream s2)

# 对象流

  • ObjectInputStream
    构造器:ObjectInputStream(InputStream in)
    实例方法:Object readObject():从 ObjectInputStream 读取对象(可能需要强转

  • ObjectOutputStream
    构造器:ObjectOutputStream(OutputStream out)
    实例方法:void writeObject(Object obj):将指定的对象写入 ObjectOutputStream

  • 对象序列化机制允许将内存中实现序列化的 Java 对象转换成字节序列(二进制流),使得对象可以脱离程序的运行而独立存在(保存到磁盘上或者通过网络传输)

  • 对象的序列化(Serialize) :将一个 Java 对象写入二进制流中

  • 对象的反序列化(Deserialize) :从二进制流中恢复该 Java 对象(反序列化时必须存在对象的字节码对象)

  • 支持序列化机制的对象的类必须实现 Serializable 接口(标识接口,无抽象方法)

  • 序列化时,会跳过 transient 或 static 关键字修饰的字段

  • 为了在反序列化时确保序列化版本的兼容性,最好在每个要序列化的类中定义一个 private static final long serialVersionUID 字段,用于标识该 Java 类的序列化版本号(具体数值可以自己定义),如果不显式定义 serialVersionUID 类变量的值,该类变量的值将由 JVM 根据类的相关信息计算

# 打印流

  • 都是输出流

  • PrintStream 构造器
    PrintStream(File file, String csn)
    PrintStream(String fileName, String csn)
    PrintStream(OutputStream out)

  • PrintWriter 构造器
    PrintWriter(File file, String csn)
    PrintWriter(String fileName, String csn)
    PrintWriter(Writer out, boolean autoFlush)
    如果 autoFlush 为 true,则在调用 println、printf 或 format 的其中一个方法时将刷新输出缓冲区,否则只会在调用 flush、close 方法时才会写出所有缓冲的输出字符

  • 实例方法
    void print(Object o):打印任意类型数据值或对象的字符串值
    void println(Object o):打印任意类型数据或对象的字符串值,然后换行
    PrintStream printf(String format, Object... args):使用指定格式字符串和参数将格式化的字符串写入此输出流

  • 标准字节打印流,输出到显示器:System.out
    标准错误字节打印流:System.err

  • System 类中重定向标准输入/输出的类方法
    void setIn(InputStream in):将 SyStem.in 的输入重定向到其它输入流
    void setOut(PrintStream out):将 SyStem.out 的输出重定向到其它打印流
    void setErr(PrintStream err):将 SyStem.err 的输出重定向到其它打印流

# 数据流

  • DataInputStream:从二进制流中读取字节,并根据所有基本类型数据进行重构
  • DataOutputStream:将数据从任意基本类型转换为一系列字节,并将这些字节写入二进制流
  • 注意:writeXxx 和 readXxx 必须要对应起来,writeByte 写出的数据,此时只能使用 readByte 读取回来

# Java 4 的 NIO

NIO与传统IO的比较

  • NIO 的三大重要组件:Channel(通道)、Buffer(缓冲区)和 Selector(选择器)
  • 通道(Channel)与传统 IO 中的流(Stream)类似,表示打开到 IO 设备(例如:文件、套接字)的连接,但通道是双向的,而 Stream 是单向的,输入流只负责输入,输出流只负责输出
  • 在 NIO 中,有 FileChannel、SocketChannel、ServerSocketChannel 和 DatagramChannel 4 种 Channel 对象,第一种是针对文件的,后三种是针对网络编程的
  • 唯一能与通道交互的组件是缓冲区(Buffer),通过通道,可以从缓冲区中读写数据
  • Buffer 对象用来缓存读写的内容,在 NIO 中,通过 Channel 对象来进行读写操作,通过 Buffer 对象缓存读写的内容
  • 选择器(Selector)可以同时监控多个 SelectableChannel 的 IO 状况,是非阻塞 IO 的核心
  • FileChannel 不能与 Selector —起使用,因为 FileChannel 不能切换到非阻塞模式
// 文件拷贝
int bufferSize = 1024; // 定义缓存区的长度
// 定义了两个 FileChaniiel 对象,用来指向待读写的文件
FileChannel src = new FileInputStream("D:\\src.txt").getChannel();
FileChannel dest = new FileOutputStream("D:\\dest.txt").getChannel();
// 通过 allocate() 方法来给 ByteBuffer 分配空间
ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
// 通过 while 循环从 src 里逐行读
while (src.read(buffer) != -1) {
    buffer.flip();      // 切换读写模式
    dest.write(buffer); // 向 dest 里写
    buffer.clear();     // 清空缓存,以便下次读
}

// 内存映射文件缓冲器:采用内存映射文件的方式来处理输入/输出,将文件或文件的一段区域映射到内存中
int length = 1024 * 1024 * 10; //10M
MappedByteBuffer buffer = new RandomAccessFile("D:\\bigFlle.text", "rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
// 写文件
for (int i = 0; i < length; i++) {
    buffer.put((byte) 'a');
}
// 只读其中的前 10 位
for (int i = 0; i < 10; i++) {
    System.out.print((char) buffer.get(i));
}

// 使用 FileChannel 的 transfreTo 方法进行流的复制
FileChannel in = FileChannel.open(Paths.get("src.txt"), StandardOpenOption.READ);
FileChannel out = FileChannel.open(Paths.get("dest.txt"), CREATE, WRITE);
in.transferTo(0, in.size(), out);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# Java 7 的 NIO.2(AIO)

# Path 接口、Files 和 Paths 工具类

  • Paths 中的类方法
    Path get(String first, String... more)
  • Files 中的用于文件拷贝的类方法
    Path copy(Path source, Path target, CopyOption... options)
    long copy(InputStream in, Path target, CopyOption... options)
    long copy(Path source, OutputStream out)
  • Files 中其它的类方法:isHidden、exists、createFile、move、deleteIfExists、newBufferedReader、newBufferedWriter、readAllLines、lines、size、write、list、walk、find、probeContentType
// 根据文件扩展名获取 MIME Type
Files.probeContentType(new File(filePath).toPath());
URLConnection.getFileNameMap().getContentTypeFor(filePath); // 识别类型不及上一方法多
new MimetypesFileTypeMap().getContentType(new File(filePath)); // 大部分返回 application/octet-stream

Files.write(Paths.get("hello.txt"), "你好 hi".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

// 注意读取超出内存大小的大文件时会出现 OOM
List<String> allLines = Files.readAllLines(Paths.get("hello.txt"), StandardCharsets.UTF_8);

// 使用使用流式处理时,注意释放资源,避免产生 too many open files
try (Stream<String> lines = Files.lines(Paths.get("hello.txt")).skip(10000).limit(10000)) {
    lines.forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 使用 FileVisitor 遍历文件和目录

  • Files 类遍历文件的类方法
    Path walkFileTree(Path start, FileVisitor<? super Path> visitor):遍历 start 路径下的所有文件和子目录
    Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor):与上一个方法的功能类似,该方法最多遍历 maxDepth 深度的文件

  • 通过继承 SimpleFileVisitor(FileVisitor的实现类)来实现自己的 “ 文件访问器”

# 使用 WatchService 监控文件变化

# 访问文件属性

Updated at: 2023-09-25 20:31:35